package com.storm.config

import com.fasterxml.jackson.databind.ObjectMapper
import com.storm.cache.AuthCacheManager
import com.storm.cache.RedisAuthCacheManager
import com.storm.cache.TokenValue
import com.storm.util.*
import com.storm.util.inter.TokenFilterInterface
import io.jsonwebtoken.Claims
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
import org.springframework.data.redis.serializer.StringRedisSerializer
import org.springframework.security.core.userdetails.UserDetailsService
import java.time.LocalDateTime
import java.util.*

@Configuration
@EnableConfigurationProperties(StormAuthConfigProperties::class, StormAuthCacheKeyProperties::class)
class StromAuthAutoConfig {

    /**
     * 判断是否需要注入token
     */
    @Bean
    fun jwtTokenComponent(stormAuthConfigProperties: StormAuthConfigProperties): JwtTokenComponent {
        return JwtTokenComponent(stormAuthConfigProperties)
    }

    @Bean
    @ConditionalOnMissingBean(TokenFilterInterface::class)
    fun tokenFilter(
        jwtTokenComponent: JwtTokenComponent,
        authCacheManager: AuthCacheManager,
        userDetailsService: UserDetailsService
    ): TokenFilterInterface {
        return DefaultTokenFilter(jwtTokenComponent, authCacheManager) { userDetailsService.loadUserByUsername(it) }
    }

    @Bean
    @ConditionalOnMissingBean(AuthCacheManager::class)
    fun authCacheManager(
        redisTemplate: RedisTemplate<String, Any>,
        objectMapper: ObjectMapper,
        authCacheKeyProperties: StormAuthCacheKeyProperties,
    ): AuthCacheManager {
        return RedisAuthCacheManager(redisTemplate, objectMapper, authCacheKeyProperties)
    }

    /**
     * 注入搜集controller中url的bean
     */
    @Bean
    fun collectPathComponent(
        authCacheManager: AuthCacheManager,
        stormAuthConfigProperties: StormAuthConfigProperties,
    ): CollectPathComponent {
        return CollectPathComponent(
            //缓存不需要权限访问的路径集合
            { authCacheManager.savePermitAuth(it) },
            //缓存需要权限访问的路径集合
            { authCacheManager.saveAuth(it) }
        ) { "true" == stormAuthConfigProperties.isCachePath }
    }

    @Bean
    @ConditionalOnMissingBean(RedisTemplate::class)
    fun redisTemplate(redisFactory: RedisConnectionFactory, objectMapper: ObjectMapper): RedisTemplate<String, Any> {
        val redisTemplate = RedisTemplate<String, Any>()
        redisTemplate.keySerializer = StringRedisSerializer()
        redisTemplate.connectionFactory = redisFactory
        redisTemplate.hashKeySerializer = StringRedisSerializer()
        val jackson2JsonRedisSerializer = Jackson2JsonRedisSerializer(Any::class.java)
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper)
        redisTemplate.hashValueSerializer = jackson2JsonRedisSerializer
        redisTemplate.valueSerializer = jackson2JsonRedisSerializer
        redisTemplate.afterPropertiesSet()
        return redisTemplate
    }
}

class JwtTokenComponent(private val stormAuthConfigProperties: StormAuthConfigProperties){
    /**
     * 创建token
     */
    fun createToken(username: String, isRememberMe: Boolean):String {
        val expiration = if (isRememberMe) stormAuthConfigProperties.expirationRemember else stormAuthConfigProperties.expiration
        val date = if (expiration > 0) Date(System.currentTimeMillis() + expiration * 1000) else Date(Long.MAX_VALUE)
        return Jwts.builder()
            .setHeaderParam("type","JWT")
            .signWith(SignatureAlgorithm.HS512, stormAuthConfigProperties.secret)
            .setIssuer(stormAuthConfigProperties.iss)
            .setSubject(username)
            .setIssuedAt(Date())
            .setExpiration(date)
            .compact()
    }
    /**
     * 从token中获取用户名
     */
    fun getUsername(token: String?):String? {
        return try {
            token?.let { getTokenBody(it).subject }
        } catch (e: Exception) {
            log.error(e.message, e)
            null
        }
    }
    /**
     * 判断token是否为空
     * 判断token是否过期
     */
    fun isUnUseful(tokenValue: TokenValue?):Boolean {
        return try {
            tokenValue?.expiration?.isBefore(LocalDateTime.now()) ?: true
        } catch (e: Exception) {
            log.error(e.message, e)
            true
        }
    }
    /**
     * 获取token中的body
     */
    private fun getTokenBody(token:String):Claims{
        return Jwts.parser()
            .setSigningKey(stormAuthConfigProperties.secret)
            .requireIssuer(stormAuthConfigProperties.iss)
            .parseClaimsJws(token)
            .body
    }

}
